package model;

import java.util.EventListener;

import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.EventListenerList;


/**
 * 
 * @author darin
 * The DefaultBounded3StateRangeModel is responsible for ensuring that:
 *  1.) degreeOffset <= 360 degrees
 *  2.) degreeRange <= 360 degrees
 *  3.) min<=cautionTripPoint<=dangerTripPoint<=max
 *  	tripPoints are given in terms of the unitRange, not degreeRange, so either the
 *  	tripPoints are handled in the UI or the model needs to know the unitRange
 *  	** min and max ARE unit ranges.
 *  The only thing the model doesn't know are the tickpoint lables and spacing.
 *  
 *  
 *   
 */
public class DefaultBounded3StateRangeModel implements Bounded3StateRangeModel
{
	double degreeOffset;
	double degreeRange;
	double min;
	double max;
	double upperCautionTripPoint;
	double upperDangerTripPoint;
	double safeTripPoint;
	double lowerCautionTripPoint=min;
	double value;

	protected EventListenerList listenerList = new EventListenerList();
    protected transient ChangeEvent changeEvent = null;
	
	//CONSTRUCTOR
    /* Only one constructor for now...will chain some later.
     * 
     */
	public DefaultBounded3StateRangeModel(double degreeOffset, double degreeRange, double min,
			double max,	double cautionTripPoint, double dangerTripPoint)
	{
		setDegreeOffset(degreeOffset);
		setDegreeRange(degreeRange);
		setMin(min);
		setMax(max);
		setUpperCautionTripPoint(cautionTripPoint);
		setUpperDangerTripPoint(dangerTripPoint);
	}
	
	/**
	 * 
	 * @return
	 */
	public double getDegreeOffset() {
		return degreeOffset;
	}
	/**
	 * 
	 * @param degreeOffset
	 */
	public void setDegreeOffset(double degreeOffset) {
		
		// let user set any offset, but only -360 to 360 makes sense
		// so mod and set the result.
		this.degreeOffset = degreeOffset % 360;
		// tell listeners something has changed.
		fireStateChanged();
	}

	public double getDegreeRange() {
		return degreeRange;
	}

	public void setDegreeRange(double degreeRange) {
		// negative degree range is not guarangeed to produce sensible results, so don't allow
		// if values outside allowable range are attempted, silently apply the min or max bound
		// instead of throwing an exception.
		if(degreeRange < 1)
			degreeRange = 1;
		if(degreeRange > 360)
			degreeRange = 360;
		this.degreeRange = degreeRange;
		fireStateChanged();
	}

	public double getMin() {
		return min;
	}

	public void setMin(double min) {

		if(min < 0)
			min = 0;
		if(min > max)
			setMax(min);
		// push the caution trip points ahead of min
		if(min > upperCautionTripPoint)
			setUpperCautionTripPoint(min);
		if(min > upperDangerTripPoint)
			setUpperDangerTripPoint(min);
		if(min > safeTripPoint)
			setSafeTripPoint(min);
		if(min > lowerCautionTripPoint)
			setLowerCautionTripPoint(min);

		this.min = min;
		fireStateChanged();
	}

	public double getMax() {
		return max;
	}

	public void setMax(double max) {

		if(max < min)
			setMin(max); // can't set a max value lower than min // push it
		if (max<upperCautionTripPoint)
			setUpperCautionTripPoint(max); // keep trip points < max
		if(max<upperDangerTripPoint)
			setUpperDangerTripPoint(max); // keep trip points < max
		//lctp is changed if needed by setUpperDangerTripPoint, so no need to handle 
		// the lower trip points here.
		this.max = max;
		fireStateChanged();
	}

	public double getUpperCautionTripPoint() {
		return upperCautionTripPoint;
	}

	public void setUpperCautionTripPoint(double newUpperCautionTripPoint) {
		if(newUpperCautionTripPoint < min)
			upperCautionTripPoint = min;
		if(newUpperCautionTripPoint > max)
			upperCautionTripPoint = max;
		if(newUpperCautionTripPoint > upperDangerTripPoint)
			setUpperDangerTripPoint(newUpperCautionTripPoint); // udtp should always be greater than uctp
		if(newUpperCautionTripPoint < safeTripPoint)
			setSafeTripPoint(newUpperCautionTripPoint); // push other trip points so that stp < uctp < udtp
		// handling lowerCautionTripPoint should be taken care of by changing safeTripPoint
		//so no need to handle it here.
		this.upperCautionTripPoint = newUpperCautionTripPoint;
		fireStateChanged();
	}

	public double getUpperDangerTripPoint() {
		return upperDangerTripPoint;
	}

	public void setUpperDangerTripPoint(double newUpperDangerTripPoint) {
		if(newUpperDangerTripPoint < min)
			upperDangerTripPoint=min;	// min <= tripPoint <= max
		if(newUpperDangerTripPoint > max)
			upperDangerTripPoint = max;
		if(newUpperDangerTripPoint < upperCautionTripPoint)
			setUpperCautionTripPoint(newUpperDangerTripPoint); // uctp <= udtp <= max
		this.upperDangerTripPoint = newUpperDangerTripPoint;
		fireStateChanged();
	}

	@Override
	public double getLowerCautionTripPoint() {
		// TODO Auto-generated method stub
		return lowerCautionTripPoint;
	}

	@Override
	public void setLowerCautionTripPoint(double newLowerCautionTripPoint) {
		if(newLowerCautionTripPoint < min)
			lowerCautionTripPoint = min; // stop at min and max bounds
		if(newLowerCautionTripPoint > max)
			lowerCautionTripPoint = max;
		// drive the other trip points ahead of lctp
		if(newLowerCautionTripPoint > safeTripPoint)
			setSafeTripPoint(newLowerCautionTripPoint);
		// no lowerDangerTripPoint to worry about
		lowerCautionTripPoint = newLowerCautionTripPoint;
		fireStateChanged();
	}
	
	public void setSafeTripPoint(double newSafeTripPoint)
	{
		// stop at min and max bounds
		if(newSafeTripPoint < min)
			newSafeTripPoint = min;
		if(newSafeTripPoint > max)
			newSafeTripPoint = max;
		// push other trip points out of the way as necessary
		if(newSafeTripPoint > upperCautionTripPoint)
			setUpperCautionTripPoint(newSafeTripPoint);
		if(newSafeTripPoint < lowerCautionTripPoint)
			setLowerCautionTripPoint(newSafeTripPoint);
		//set the value and fire listeners
		safeTripPoint = newSafeTripPoint;
		fireStateChanged();
	}
	public double getSafeTripPoint()
	{
		return safeTripPoint;
	}
	
	public double getValue()
	{
		return this.value;
	}
	
	public void setValue(double newValue)
	{
		// stop at min/max bounds
		if(newValue < min)
			newValue = min;
		if(newValue > max)
			newValue = max;
		value = newValue;
		
		fireStateChanged();
	}
// LISTENERS -- BORROWED FROM JSLIDER
    /**
     * Adds a <code>ChangeListener</code>.  The change listeners are run each
     * time any one of the Bounded Range model properties changes.
     *
     * @param l the ChangeListener to add
     * @see #removeChangeListener
     * @see BoundedRangeModel#addChangeListener
     */
    public void addChangeListener(ChangeListener l) {
        listenerList.add(ChangeListener.class, l);
    }


    /**
     * Removes a <code>ChangeListener</code>.
     *
     * @param l the <code>ChangeListener</code> to remove
     * @see #addChangeListener
     * @see BoundedRangeModel#removeChangeListener
     */
    public void removeChangeListener(ChangeListener l) {
        listenerList.remove(ChangeListener.class, l);
    }


    /**
     * Returns an array of all the change listeners
     * registered on this <code>DefaultBoundedRangeModel</code>.
     *
     * @return all of this model's <code>ChangeListener</code>s
     *         or an empty
     *         array if no change listeners are currently registered
     *
     * @see #addChangeListener
     * @see #removeChangeListener
     *
     * @since 1.4
     */
    public ChangeListener[] getChangeListeners() {
        return listenerList.getListeners(ChangeListener.class);
    }


    /**
     * Runs each <code>ChangeListener</code>'s <code>stateChanged</code> method.
     *
     * @see #setRangeProperties
     * @see EventListenerList
     */
    protected void fireStateChanged()
    {
        Object[] listeners = listenerList.getListenerList();
        for (int i = listeners.length - 2; i >= 0; i -=2 ) {
            if (listeners[i] == ChangeListener.class) {
                if (changeEvent == null) {
                    changeEvent = new ChangeEvent(this);
                }
                ((ChangeListener)listeners[i+1]).stateChanged(changeEvent);
            }
        }
    }
    
    public <T extends EventListener> T[] getListeners(Class<T> listenerType) {
        return listenerList.getListeners(listenerType);
    }
	
}
